package character;

import modifier.character.CharacterModifier;
import board.BoardUtil;
import board.PatternType;
import board.Tile;
import character.ai.AIGeneralControl;
import character.ai.AILogicSettings;
import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.control.Control;
import effect.character.area.AreaCharacterEffectEnum;
import effect.character.area.AreaCharacterEffectFactory;
import effect.character.single.SingleCharacterEffectEnum;
import effect.character.single.SingleCharacterEffectFactory;
import effect.tile.TileEffectEnum;
import effect.tile.TileEffectFactory;
import game.DataStore;
import game.GameUtil;
import game.combat.CombatActor;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import player.Player;
import misc.Vector3i;

public class MyCharacter extends Node implements CombatActor {
    public static final float RADIUS = 0.381f;
    public static final float HEIGHT = 1.8f;
    protected int id;
    protected Player player;
    protected CharacterInventory inventory;
    protected CharacterSettings characterSettings;
    protected CharacterStats characterStats;
    protected CharacterStatus characterStatus;
    protected Map<CharacterModifier, CharacterModifier> characterModifiers = new HashMap<CharacterModifier, CharacterModifier>();
    protected Set<Vector3i> occupiedLocations = new HashSet<Vector3i>();
    protected Set<Vector3i> visibleLocations = new HashSet<Vector3i>();
    protected Vector3i boardLocation, queuedLocation;

    @Deprecated
    protected MyCharacter() {}

    public MyCharacter(CharacterSize characterSize, Player player) {
        super("character");
        this.player = player;
        init(characterSize, null);
    }
    
    public MyCharacter(CharacterSize characterSize, Player player, AILogicSettings aiLogicSettings) {
        super("character");
        this.player = player;
        init(characterSize, aiLogicSettings);
    }

    private void init(CharacterSize characterSize, AILogicSettings aiLogicSettings) {
        id = GameUtil.getNextCharacterId();

	player.addCharacter(this);
        
        Node charModel = (Node)DataStore.assetManager.loadModel("Models/Oto/Oto.mesh.xml");
        attachChild(charModel);
        scale(HEIGHT/10);
        
        getModel().getControl(AnimControl.class).createChannel();

	characterSettings = new CharacterSettings(this);
	characterSettings.setCharacterSize(characterSize);

	characterStats = new CharacterStats(this);
	characterStats.setMaxHitPoints(30);
	characterStats.setSightRange(4);
	characterStats.setMoveSpeed(5);
        characterStats.addAbility(1, AreaCharacterEffectFactory.getAreaCharacterEffect(this, AreaCharacterEffectEnum.EXPLOSION));
        characterStats.addAbility(2, AreaCharacterEffectFactory.getAreaCharacterEffect(this, AreaCharacterEffectEnum.LIGHTNING));
        characterStats.addAbility(3, AreaCharacterEffectFactory.getAreaCharacterEffect(this, AreaCharacterEffectEnum.POISON_CLOUD));
        characterStats.addAbility(4, AreaCharacterEffectFactory.getAreaCharacterEffect(this, AreaCharacterEffectEnum.MASS_HEAL));
        characterStats.addAbility(5, SingleCharacterEffectFactory.getSingleCharacterEffect(this, SingleCharacterEffectEnum.HEAL));
        characterStats.addAbility(6, SingleCharacterEffectFactory.getSingleCharacterEffect(this, SingleCharacterEffectEnum.HASTE));
        characterStats.addAbility(7, TileEffectFactory.getTileEffect(this, TileEffectEnum.HEALING_AURA));
        characterStats.addAbility(8, TileEffectFactory.getTileEffect(this, TileEffectEnum.TRAP_EXPLOSION));
        characterStats.addAbility(9, TileEffectFactory.getTileEffect(this, TileEffectEnum.SIGHT));
        
        characterStatus = new CharacterStatus(this);
        characterStatus.setHitPoints(characterStats.getMaxHitPoints());

        inventory = new CharacterInventory(this);
        
        if(aiLogicSettings != null) {
            addControl(new AIGeneralControl(aiLogicSettings));
        }
    }

    @Override
    public void turnStarting() {
        refreshPerTurnVars();
        tickModifiers();
    }
    
    protected void refreshPerTurnVars() {
        CharacterCombatControl control = getControl(CharacterCombatControl.class);
        if(control != null) {
            control.setTicksUsed(0);
            control.setAttackOfOpportunity(true);
        }
    }
    
    protected void tickModifiers() {
        Iterator<CharacterModifier> iter = getCharacterModifiers().iterator();
	while(iter.hasNext()) {
	    CharacterModifier statusEffect = iter.next();
	    statusEffect.tickModifier();
	    if(statusEffect.isMarkedForRemoval())
		iter.remove();
	}
    }
    
    //Implementing the CombatActor interface
    @Override
    public int getInitiative() {
        return characterStatus.getInitiative();
    }
    @Override
    public void setInitiative(int initiative) {
        characterStatus.setInitiative(initiative);
    }
    @Override
    public boolean isFirstTurn() {
        return characterStatus.isFirstTurn();
    }
    @Override
    public void setFirstTurn(boolean firstTurn) {
        characterStatus.setFirstTurn(firstTurn);
    }

    public int getId() {
        return id;
    }

    public Vector3i getBoardLocation() {
        return boardLocation;
    }
    public void setBoardLocation(Vector3i boardLocation) {
        if(boardLocation != null && !boardLocation.equals(this.boardLocation)) {
            moveLocation(boardLocation);
        }
    }
    public void warpToBoardLocation() {
        Vector3f translation = BoardUtil.boardToWorld(boardLocation).add(0f, Tile.HEIGHT/2+HEIGHT/2, 0f);
        setLocalTranslation(translation);
    }
    public Vector3i getQueuedLocation() {
        return queuedLocation;
    }
    public void setQueuedLocation(Vector3i queuedLocation) {
        this.queuedLocation = queuedLocation;
    }

    public Collection<CharacterModifier> getCharacterModifiers() {
        return characterModifiers.values();
    }
    public void addCharacterModifier(CharacterModifier characterModifier) {
        if(!characterModifiers.containsKey(characterModifier)) {
            CharacterModifier tileModifierClone = characterModifier.cloneForCharacter(this);
            characterModifiers.put(tileModifierClone, tileModifierClone);
            tileModifierClone.applyModifier();
        } else if(characterModifiers.containsKey(characterModifier) && characterModifier.getTicksRemaining() >= characterModifiers.get(characterModifier).getTicksRemaining()) {
            System.out.println("Refreshing ticks remaining for "+characterModifier+" to "+characterModifier.getTicksRemaining()+" ticks.");
            characterModifiers.get(characterModifier).setTicksRemaining(characterModifier.getTicksRemaining());
        }
    }
    public void addCharacterModifiers(Collection<CharacterModifier> characterModifiers) {
        for(CharacterModifier statusModifier : characterModifiers)
            addCharacterModifier(statusModifier);
    }
    public void removeCharacterModifier(CharacterModifier characterModifier) {
        characterModifier.abortModifier();
	characterModifiers.remove(characterModifier);
    }

    public Collection<Vector3i> getOccupiedLocations() {
	return occupiedLocations;
    }
    protected void populateOccupiedLocations() {
	occupiedLocations.clear();
	occupiedLocations.addAll(CharacterUtil.getOccupiedLocations(boardLocation, characterSettings.getCharacterSize()));
    }

    public Collection<Vector3i> getVisibleLocations() {
	return visibleLocations;
    }
    protected void populateVisibleLocations() {
	visibleLocations.clear();
	for(Vector3i occupiedLocation : occupiedLocations)
	    visibleLocations.addAll(BoardUtil.getPatternLocations(PatternType.DIAMOND, characterStats.getSightRange(), true, occupiedLocation));
    }

    public void moveLocation(Vector3i newLocation) {
        Set<Vector3i> oldBoardLocations = new HashSet<Vector3i>(occupiedLocations);
        boardLocation = newLocation;
        queuedLocation = newLocation;
	populateOccupiedLocations();
        if(getControl(MyCharacterControl.class) != null)
            getControl(MyCharacterControl.class).repopulateValidMoveLocations();
	populateVisibleLocations();
        DataStore.board.characterMoved(this, oldBoardLocations);
        System.out.println(boardLocation);
    }

    public CharacterSettings getCharacterSettings() {
	return characterSettings;
    }
    public CharacterStats getCharacterStats() {
	return characterStats;
    }
    public CharacterStatus getCharacterStatus() {
        return characterStatus;
    }

    public CharacterInventory getInventory() {
        return inventory;
    }

    public Player getPlayer() {
        return player;
    }
    
    public Node getModel() {
	return (Node)getChild(0);
    }
    
    @Override
    public void addControl(Control control) {
        super.addControl(control);
        if(control instanceof MyCharacterControl) {
            AnimControl animControl = getModel().getControl(AnimControl.class);
            if(animControl != null) {
                AnimChannel walkChannel = animControl.getChannel(MyCharacterControl.ANIM_CHANNEL_WALK);
                walkChannel.setAnim(MyCharacterControl.ANIM_NAME_STAND);
                animControl.clearListeners();
                animControl.addListener(getControl(MyCharacterControl.class));
            }
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final MyCharacter other = (MyCharacter) obj;
        if (this.id != other.id) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 97 * hash + this.id;
        return hash;
    }

    @Override
    public String toString() {
        return "Character "+id;
    }
}
